/**************************************************************
 * client_xmms2.c - 
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/


#include <xmmsclient/xmmsclient.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>

#include "client.h"

struct xmms2_callback{
  void (*callback)(void *data);
  void *data;
};

struct xmms2_control{
  xmmsc_connection_t *con;
  xmmsc_result_t *lasterr;
  GHashTable *config;
  gchar *errfmt;
  gchar *ipcpath;
  gchar *execpath;
  gchar *name;
  gboolean connected;
  gboolean auto_start_server;
  gboolean verbose;
/*   GMainLoop *ml; */
/*   void *ml_data; */
/*   GThread *thread; */
  GSList *file_changed;
  GSList *started;
  GSList *stoped;
  GSList *exited;
};


static gboolean __start_server(void *data);

#define DEFINE_PLAYBACK_FUNCTION(name, xmmsc_fun)					\
  static gboolean name(void *data)									\
  {																	\
    struct xmms2_control *ctl = (struct xmms2_control *)data;		\
    xmmsc_result_t *res;											\
																	\
    if(! ctl->connected)											\
	  if(ctl->auto_start_server)  {									\
		if(! __start_server(data))									\
		  return FALSE;												\
	  }else															\
		return FALSE;												\
    res = xmmsc_playback_##xmmsc_fun(ctl->con);						\
    xmmsc_result_wait(res);											\
																	\
    SET_ERR_AND_RETURN(ctl, res, "** Can't " #xmmsc_fun ":%s\n");	\
  }


#define SET_ERR_AND_RETURN(ctl,res,fmt)			\
  do {											\
    RETURN_IF_ERR((ctl),(res),(fmt));			\
    xmmsc_result_unref((res));					\
    return TRUE;								\
  }while(0)


#define RETURN_IF_ERR(ctl,res,fmt)		\
  RETURN_IF_ERR_VAL(ctl,res,fmt,FALSE)

#define RETURN_IF_ERR_VAL(ctl,res,fmt,val)		\
  if(xmmsc_result_iserror((res)))  {			\
    (ctl)->errfmt = (fmt);						\
    if((ctl)->lasterr)							\
      xmmsc_result_unref((ctl)->lasterr);		\
    (ctl)->lasterr = (res);						\
    return (val);								\
  }

#define RETURN_FOR_RESULT_ERR(ctl,res,type)		\
  RETURN_FOR_RESULT_ERR_VAL((ctl),(res),type,FALSE)


#define RETURN_FOR_RESULT_ERR_VAL(ctl,res,type,val)					\
  do {																\
    if((ctl)->lasterr)  {											\
      xmmsc_result_unref((ctl)->lasterr);							\
      (ctl)->lasterr = NULL;										\
    }																\
    (ctl)->errfmt = "** Can't get " type " from xmmsc_result_t.\n";	\
    return (val);													\
  }while(0)


void *checked_malloc(size_t size)
{
  void *p=malloc(size);
  if(!p) {
    fprintf(stderr,"**can not alloc memory!!!abortting...\n");
    exit (-1);
  }
  memset(p,0,size);
  return p;
}



DEFINE_PLAYBACK_FUNCTION(xmms2_play, start);
DEFINE_PLAYBACK_FUNCTION(xmms2_stop, stop);
DEFINE_PLAYBACK_FUNCTION(xmms2_pause, pause);


static gboolean xmms2_toggle(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_result_t *res;
  uint32_t status;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_status(ctl->con);
  xmmsc_result_wait(res);
  
  RETURN_IF_ERR(ctl, res, "** Can't get playback_status:%s");

  if(! xmmsc_result_get_uint(res, &status))
    RETURN_FOR_RESULT_ERR(ctl, res, "uint");

  if(status == XMMS_PLAYBACK_STATUS_PLAY)
    xmms2_pause(data);
  else
    xmms2_play(data);

  return TRUE;
}


static gboolean xmms2_quit(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_result_t *res;
  uint32_t status;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_quit(ctl->con);
  xmmsc_result_wait(res);

  SET_ERR_AND_RETURN(ctl, res, "** Can't shut XMMS2 server.\n");
}


static gboolean xmms2_seek(void *data, gint msec)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_result_t *res;
 
  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_seek_ms(ctl->con, msec);
  xmmsc_result_wait(res);

  SET_ERR_AND_RETURN(ctl, res, "** Seek error:%s");
}


static gboolean xmms2_seek_rel(void *data, int msec)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_result_t *res;
 
  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_seek_ms_rel(ctl->con, msec);
  xmmsc_result_wait(res);

  SET_ERR_AND_RETURN(ctl, res, "** Seek error:%s");
}

static gint xmms2_volume_get(void *data, gint channel)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_result_t *res;
  uint32_t left, right;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_volume_get(ctl->con);
  xmmsc_result_wait(res);

  RETURN_IF_ERR_VAL(ctl, res, "** Can't get volume: %s", -1);

  if(! xmmsc_result_get_dict_entry_uint(res, "left", &left)
     || ! xmmsc_result_get_dict_entry_uint(res, "right", &right))
    RETURN_FOR_RESULT_ERR_VAL(ctl, res, "dict:uint", -1);

  xmmsc_result_unref(res);

  switch(channel)  {
  case VOLUME_CHANNEL_LEFT:
    return left;
  case VOLUME_CHANNEL_RIGHT:
    return right;
  case VOLUME_CHANNEL_ALL:
    return (left + right)/2;
  }
}


static gboolean xmms2_volume_set(void *data, gint channel, gint vol)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_result_t *res;
  uint32_t left = -1, right = -1;

  if(! ctl->connected)
    return FALSE;

  switch(channel)  {
  case VOLUME_CHANNEL_ALL:
    right = CLAMP(vol, 0, 100);;
  case VOLUME_CHANNEL_LEFT:
    left = CLAMP(vol, 0, 100);
    break;
  case VOLUME_CHANNEL_RIGHT:
    right = CLAMP(vol, 0, 100);;
    break;
  }

  if(left != -1)  {
    res = xmmsc_playback_volume_set(ctl->con, "left", left);
    xmmsc_result_wait(res);

    RETURN_IF_ERR(ctl, res, "** Can's set left vhannel volume: %s");
  }

  if(right != -1)  {
    res = xmmsc_playback_volume_set(ctl->con, "left", right);
    xmmsc_result_wait(res);

    RETURN_IF_ERR(ctl, res, "** Can's set left vhannel volume: %s");
  }

  return TRUE;
}


static gboolean do_reljump(struct xmms2_control *ctl, gint where)
{
  xmmsc_connection_t *conn = ctl->con;
  xmmsc_result_t *res;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playlist_set_next_rel (conn, where);
  xmmsc_result_wait (res);

  RETURN_IF_ERR(ctl, res, "** Couldn't advance in playlist: %s");

  res = xmmsc_playback_tickle (conn);
  xmmsc_result_wait (res);

  SET_ERR_AND_RETURN(ctl, res, "** %s");
}

static gboolean xmms2_next(void *data)
{
  return do_reljump((struct xmms2_control *)data, 1);
}


static gboolean xmms2_prev(void *data)
{
  return do_reljump((struct xmms2_control *)data, -1);
}


static gint xmms2_status(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_connection_t *con = ctl->con;
  xmmsc_result_t *res;
  uint32_t status;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_status(con);
  xmmsc_result_wait(res);

  if(xmmsc_result_iserror(res))  {
    if(ctl->lasterr)
      xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = res;
    ctl->errfmt = "** Can't get playback status: %s";
    return -1;
  }

  if(! xmmsc_result_get_uint(res, &status))
    RETURN_FOR_RESULT_ERR(ctl, res, "uint");

  xmmsc_result_unref(res);
  return status;
}


static gint xmms2_time(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_connection_t *con = ctl->con;
  xmmsc_result_t *res;
  uint32_t time;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_playtime(con);
  xmmsc_result_wait(res);

  if(xmmsc_result_iserror(res))  {
    if(ctl->lasterr)
      xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = res;
    ctl->errfmt = "** Can't get playback time: %s";
    return -1;
  }

  if(! xmmsc_result_get_uint(res, &time))
    RETURN_FOR_RESULT_ERR_VAL(ctl, res, "uint", -1);

  xmmsc_result_unref(res);
  return time;
}


static gint xmms2_duration(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_connection_t *con = ctl->con;
  xmmsc_result_t *res;
  uint32_t id;
  const char *raw_value;
  uint32_t value;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_current_id(con);
  xmmsc_result_wait(res);

  if(xmmsc_result_iserror(res))  {
    if(ctl->lasterr)
      xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = res;
    ctl->errfmt = "** Can't get current id: %s";
    return 0;
  }

  if(! xmmsc_result_get_uint(res, &id))
    RETURN_FOR_RESULT_ERR_VAL(ctl, res, "uint", 0);

  xmmsc_result_unref(res);

  res = xmmsc_medialib_get_info (con, id);
  xmmsc_result_wait (res);

  if(xmmsc_result_iserror(res))  {
    if(ctl->lasterr)
      xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = res;
    ctl->errfmt = "** %s";
    return 0;
  }

  if(! xmmsc_result_get_dict_entry_uint(res, "duration", &value))
    RETURN_FOR_RESULT_ERR_VAL(ctl, res, "dict:uint", 0);
  xmmsc_result_unref(res);

  return value;
}

static char *unescape(const char *str)
{
  char *buf, *p;
  int x;

  if(! str)
    return NULL;

  buf = (char *)checked_malloc(strlen(str) + 1);
  p = buf;

  while(*str) {
    if(*str != '%') {
      *p++ = *str++;
      continue;
    }

    str++;
    if(sscanf(str,"%2x",&x)) {
      *p++ = x;
      str += 2;
      continue;
    }

    *p++ = *str++;
  }
  *p = '\0';

  return buf;
}


static gchar *get_key_value(void *data, const gchar *key, gint need_unescape)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_connection_t *con = ctl->con;
  xmmsc_result_t *res;
  uint32_t id;
  const char *raw_value;
  gchar *value;

  if(! ctl->connected)
    return FALSE;

  res = xmmsc_playback_current_id(con);
  xmmsc_result_wait(res);

  if(xmmsc_result_iserror(res))  {
    if(ctl->lasterr)
      xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = res;
    ctl->errfmt = "** Can't get current id: %s";
    return (gchar *)-1;
  }

  if(! xmmsc_result_get_uint(res, &id))
    RETURN_FOR_RESULT_ERR_VAL(ctl, res, "uint", (gchar *)-1);

  xmmsc_result_unref(res);

  res = xmmsc_medialib_get_info (con, id);
  xmmsc_result_wait (res);

  if(xmmsc_result_iserror(res))  {
    if(ctl->lasterr)
      xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = res;
    ctl->errfmt = "** %s";
    return (gchar *)-1;
  }

  if(! xmmsc_result_get_dict_entry_string(res, key, &raw_value))
    RETURN_FOR_RESULT_ERR_VAL(ctl, res, "dict:string", NULL);

  if(! strcmp(key, "url"))
	value = g_strdup(xmmsc_result_decode_url(res, raw_value));
  else  {
	if(raw_value)
	  if(need_unescape)
		value = unescape(raw_value);
	  else
		value = strdup(raw_value);
  }

  xmmsc_result_unref(res);
  return value;
}


static gchar *xmms2_get_music_file(void *data)
{
  return  get_key_value(data, "url", FALSE);
}

/*
 *check if @fname is valid, and do modifications if nedded
 *FIXME:format fname needed.
 */
static gchar *url_check_and_format(const gchar *fname)
{
  gchar *url;

  /* urls usually are preceded with some markings  */
  url = g_new(gchar, PATH_MAX + 10);
  if(! realpath(fname, url+7))  {
    g_free(url);
    return NULL;
  }

  if(! g_file_test(fname, G_FILE_TEST_IS_REGULAR))
    return NULL;

  return strncpy(url, "file://", 7);
}


static gboolean xmms2_play_music_file(void *data, const gchar *filename)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;
  xmmsc_connection_t *con = ctl->con;
  xmmsc_result_t *res, *list;
  uint32_t pos;
  const char *name;

  if(! ctl->connected)
    return FALSE;

  if(! filename)  {
    ctl->errfmt = "** Invalid argument.\n";
    return FALSE;
  }

  list = xmmsc_playlist_current_active(con);
  xmmsc_result_wait(list);

  RETURN_IF_ERR(ctl, list, "** Can't get playlist name: %s");

  if(! xmmsc_result_get_string(list, &name))
    RETURN_FOR_RESULT_ERR(ctl, res, "string");

  res = xmmsc_playlist_current_pos(con, name);
  xmmsc_result_wait(res);

  RETURN_IF_ERR(ctl, res, "** Can't get current position: %s");

  if(! xmmsc_result_get_dict_entry_uint(res, "position", &pos))  {
    xmmsc_result_unref(list);
    RETURN_FOR_RESULT_ERR(ctl, res, "dict:\"position\":uint");
  }

  xmmsc_result_unref(res);

  filename = url_check_and_format(filename);

  if(! filename)  {
    ctl->errfmt = "** Filename error.\n";
    return FALSE;
  }

  res = xmmsc_playlist_insert_url(con, name, ++pos, filename);
  xmmsc_result_wait(res);

  g_free((void *)filename);

  RETURN_IF_ERR(ctl, res, "** Error inserting url: %s");

  xmmsc_result_unref(list);

  return xmms2_next(data);
}


static gchar *xmms2_get_artist(void *data)
{
  return get_key_value(data, "artist", FALSE);
}

static gchar *xmms2_get_title(void *data)
{
  return get_key_value(data, "title", FALSE);
}

static gchar *xmms2_get_album(void *data)
{
  return get_key_value(data, "album", FALSE);
}


static gchar *xmms2_get_year(void *data)
{
  return get_key_value(data, "year", FALSE);
}

static gchar *xmms2_get_genre(void *data)
{
  return get_key_value(data, "genre", FALSE);
}


static gchar *xmms2_get_track(void *data)
{
  return get_key_value(data, "track", FALSE);
}


static gchar *xmms2_get_comment(void *data)
{
  return get_key_value(data, "comment", FALSE);
}


static void do_callback(xmmsc_result_t *res, void *udata)
{
  GSList *list = (GSList *)udata;
  struct xmms2_callback *cb;

  //  fprintf(stderr, "CLIENT XMMS2: callback called.\n");

  if(! list)  return;
  do{
	cb = (struct xmms2_callback *)list->data;
	(cb->callback)(cb->data);
  }while(list = g_slist_next(list));
}


static void do_status_changed(xmmsc_result_t *res, void *udata)
{
  struct xmms2_control *ctl = (struct xmms2_control *)udata;
  gint status;

  status = xmms2_status(ctl);
  switch(status)  {
  case PLAYER_STATUS_STOP:
  case PLAYER_STATUS_PAUSE:
	do_callback(res, ctl->stoped);
	break;
  case PLAYER_STATUS_PLAY:
	do_callback(res, ctl->started);
	break;
  default:
	break;
  }
}

static gboolean __xmms2_set_callback(GSList **list, void (*callback)(void *udata), void *udata)
{
  struct xmms2_callback *cb;

  cb = g_new(struct xmms2_callback, 1);
  cb->callback = callback;
  cb->data = udata;
  *list = g_slist_prepend(*list, cb);

  return TRUE;
}


static gboolean xmms2_set_file_changed(void *data, void (*callback)(void *udata), void *udata)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  if(! ctl || ! callback)  return FALSE;
  return __xmms2_set_callback(&ctl->file_changed, callback, udata);
}

static gboolean xmms2_set_player_started(void *data, void (*callback)(void *udata), void *udata)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  if(! ctl || ! callback)  return FALSE;
  return __xmms2_set_callback(&ctl->started, callback, udata);
}


static gboolean xmms2_set_player_stoped(void *data, void (*callback)(void *udata), void *udata)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  if(! ctl || ! callback)  return FALSE;
  return __xmms2_set_callback(&ctl->stoped, callback, udata);
}


static gboolean xmms2_set_player_exited(void *data, void (*callback)(void *udata), void *udata)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  if(! ctl || ! callback)  return FALSE;
  return __xmms2_set_callback(&ctl->exited, callback, udata);
}


static gboolean xmms2_is_error(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  return (ctl->lasterr || ctl->errfmt);
}

static void xmms2_clear_error(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  if(ctl->lasterr)  {
    xmmsc_result_unref(ctl->lasterr);
    ctl->lasterr = NULL;
  }

  if(ctl->errfmt)
    ctl->errfmt = NULL;
}

static void xmms2_perror(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

  if(! ctl->connected)  {
    fprintf(stderr, "** Not connected or lost connection.\n");
    return;
  }

  if(ctl->errfmt)  {
    if(ctl->lasterr)
      fprintf(stderr, ctl->errfmt, xmmsc_result_get_error(ctl->lasterr));
    else
      fprintf(stderr, ctl->errfmt);
  }else  {
    if(ctl->lasterr)
      fprintf(stderr, xmmsc_result_get_error(ctl->lasterr));
    else
      fprintf(stderr, "-- Everything seems fine.\n");
  }
}



/*
 * Config functions. code from XMMS2 client/cli
 */
static GHashTable *
parse_config (const gchar *buffer)
{
  gchar **split;
  gint i;
  GHashTable *config;

  config = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  split = g_strsplit (buffer, "\n", 0);
  for (i = 0; split && split[i]; i++) {
    gchar **s;

    s = g_strsplit (split[i], "=", 2);
    if (s && s[0] && s[1]) {
      if (g_ascii_strcasecmp (s[1], "NULL") == 0) {
	g_hash_table_insert (config, g_strdup (s[0]), NULL);
      } else {
	g_hash_table_insert (config, g_strdup (s[0]), g_strdup (s[1]));
      }
    }
    g_strfreev (s);
  }
  g_strfreev (split);

  return config;
}


static GHashTable *__read_config (const gchar *conffile)
{
  GHashTable *config;
  gchar *buffer, *file;
  gint read_bytes = 0;
  struct stat st;
  FILE *fp;
  gchar defaultconfig[] = "ipcpath=NULL\nauto_start_server=true\nserver_exec=xmms2-launcher\n";
  gchar userconf[PATH_MAX];

  xmmsc_userconfdir_get (userconf, PATH_MAX);
  file = g_build_path (G_DIR_SEPARATOR_S, userconf,
		       "clients", conffile, NULL);

  if (! g_file_test (file, G_FILE_TEST_EXISTS)) {
    gchar *dir = g_build_path (G_DIR_SEPARATOR_S, userconf, "clients", NULL);
    g_mkdir_with_parents (dir, 0755);
    g_free (dir);

    fp = fopen (file, "w+");
    if (fp) {
      fwrite (defaultconfig, strlen(defaultconfig), 1, fp);
      fclose (fp);
    } else {
      g_free(file);
      return parse_config(defaultconfig);
      /* ignore the config file, just use the default */
      //  print_info ("Could not create configfile: %s\nMake sure you have write permissions to that location.", file);
    }
  }

  if (g_file_test (file, G_FILE_TEST_EXISTS)) {
    fp = fopen (file, "r");
    if (!fp) {
      g_free(file);
      return parse_config(defaultconfig);
      //      print_error ("Could not open configfile %s", file);
    }
    g_free (file);

    if (fstat (fileno (fp), &st) == -1) {
      return parse_config(defaultconfig);
      // print_error ("fstat");
    }

    buffer = g_malloc0 (st.st_size + 1);

    while (read_bytes < st.st_size) {
      guint ret = fread (buffer + read_bytes,
			 st.st_size - read_bytes, 1, fp);

      if (ret == 0) {
	break;
      }

      read_bytes += ret;
      g_assert (read_bytes >= 0);
    }

    config = parse_config (buffer);

    g_free (buffer);
  } else {
    config = parse_config (defaultconfig);
  }

  return config;
}


static void  __free_config (GHashTable *config)
{
  if (config) {
    g_hash_table_destroy (config);
  }
}


static void xmms2_disconnect_cb(void *data)
{
  struct xmms2_control *ctl = (struct xmms2_control *)data;

/*   g_main_loop_quit(ctl->ml); */
/*   xmmsc_mainloop_gmain_shutdown(ctl->con, ctl->ml_data); */
  do_callback(NULL, ctl->exited);
  ctl->connected = FALSE;
}


static gboolean __start_server(void *data)
{
  struct xmms2_control *control = (struct xmms2_control *)data;
  gchar *path;
  GHashTable *config = control->config;
  const gchar *real_exec;

  if(! control->con)  {
	control->con = xmmsc_init(control->name);
	if(! control->con)  {
	  fprintf(stderr, "** Can't init the xmms connection!\n");
	  return FALSE;
	}
  }

  path = getenv("XMMS_PATH");
  if(! path)
    path = g_hash_table_lookup(config, "ipcpath");

  if(! path)
    path = NULL;

  if(! xmmsc_connect(control->con, path))  {
    if(! strcasecmp(g_hash_table_lookup(config, "auto_start_server"), "true"))  {
	  real_exec = control->execpath;
      if(! system(real_exec))  {
		if(! xmmsc_connect(control->con, path))  {
		  fprintf(stderr, "** Can't connect to the server.\n");
		  return FALSE;
		}
      }else  {
		fprintf(stderr, "** Can't launch the xmms server!\n");
		return FALSE;
      }
    }else  {
      fprintf(stderr, "** XMMS server not started!\n");
	  return FALSE;
    }
  }

  control->connected = TRUE;
  return TRUE;
}


static struct client *client_xmms2_init(const gchar *client_name,
										const gchar *exec,
										gint argc, gchar **argv)
{
  struct client *client;
  struct xmms2_control *control;
  xmmsc_connection_t *connection;
  gchar *path;
  GHashTable *config;
  gchar *defaultname = "universal-media-control";
  const gchar *name;
  gint ret;
  const gchar *real_exec;

  if(client_name && *client_name)
    name = client_name;
  else
    name = defaultname;

  client = g_new0(struct client, 1);
  control = g_new0(struct xmms2_control, 1);
  client->c_servername = "XMMS2";
  client->c_data = control;

  config = __read_config(name);
  if(! config)  {
	fprintf(stderr, "FAILED READ CONFIG.\n");
	exit(1);
  }

  connection = xmmsc_init(name);
  if(! connection)  {
    fprintf(stderr, "** Can't init the xmms connection!\n");
    goto err_cleanup;
  }

  path = getenv("XMMS_PATH");
  if(! path)
    path = g_hash_table_lookup(config, "ipcpath");

  if(! path)
    path = NULL;

  if(! xmmsc_connect(connection, path))  {
    if(! strcasecmp(g_hash_table_lookup(config, "auto_start_server"), "true"))  {
	  control->auto_start_server = TRUE;
      if(exec && *exec)
		real_exec = exec;
      else
		real_exec = g_hash_table_lookup(config, "server_exec");

      if(! system(real_exec))  {
		if(!xmmsc_connect(connection, path))  {
		  fprintf(stderr, "** Can't connect to the server.\n");
		  goto err_cleanup;
		}
      }else  {
		fprintf(stderr, "** Can't launch the xmms server!\n");
		goto err_cleanup;
      }
    }else  {
      fprintf(stderr, "** XMMS server not started!\n");
      goto err_cleanup;
    }
  }


  XMMS_CALLBACK_SET (connection, xmmsc_broadcast_playback_current_id,
					 do_callback, control->file_changed);
  XMMS_CALLBACK_SET (connection, xmmsc_playback_current_id,
					 do_callback, control->file_changed);
  XMMS_CALLBACK_SET (connection, xmmsc_broadcast_playback_status,
					 do_status_changed, control);
  XMMS_CALLBACK_SET (connection, xmmsc_playback_status,
					 do_status_changed, control);

/*   control->ml = g_main_loop_new(NULL, FALSE); */
/*   control->ml_data = xmmsc_mainloop_gmain_init(connection); */
/*   if(! g_thread_supported()) */
/* 	g_thread_init(NULL); */
/*   control->thread = g_thread_create(g_main_loop_run, control->ml, FALSE, NULL); */
/*   if(! control->thread)  { */
/* 	fprintf(stderr, "error, can't spawn thread.\n"); */
/* 	xmmsc_unref(connection); */
/* 	xmmsc_mainloop_gmain_shutdown(control->con, control->ml_data); */
/* 	g_main_loop_quit(control->ml); */
/* 	goto err_cleanup; */
/*   } */

  control->con = connection;
  control->connected = TRUE;
  control->config = config;
  control->execpath = g_strdup(real_exec);
  control->name = g_strdup(name);
  xmmsc_disconnect_callback_set(connection, xmms2_disconnect_cb, control);

  client->play = xmms2_play;
  client->stop = xmms2_stop;
  client->pause = xmms2_pause;
  client->toggle = xmms2_toggle;
  client->quit = xmms2_quit;

  client->seek = xmms2_seek;
  client->seek_rel = xmms2_seek_rel;

  client->volume_get = xmms2_volume_get;
  client->volume_set = xmms2_volume_set;

  client->next = xmms2_next;
  client->prev = xmms2_prev;

  client->status = xmms2_status;
  client->time = xmms2_time;
  client->duration = xmms2_duration;

  client->get_music_file = xmms2_get_music_file;
  client->play_music_file = xmms2_play_music_file;

  client->get_artist = xmms2_get_artist;
  client->get_title = xmms2_get_title;
  client->get_album = xmms2_get_album;
  client->get_year = xmms2_get_year;
  client->get_genre = xmms2_get_genre;
  client->get_track = xmms2_get_track;
  client->get_comment = xmms2_get_comment;

  client->set_file_changed = xmms2_set_file_changed;
  client->set_player_started = xmms2_set_player_started;
  client->set_player_stoped = xmms2_set_player_stoped;
  client->set_player_exited = xmms2_set_player_exited;

  client->is_error = xmms2_is_error;
  client->clear_error = xmms2_clear_error;
  client->perror = xmms2_perror;

  return client;

 err_cleanup:
  g_free(control);
  g_free(client);
  __free_config(config);
  return NULL;
}


static void __free_elem(gpointer data, gpointer udata)
{
  g_free(data);
}


static void client_xmms2_exit(struct client *cl)
{
  struct xmms2_control *ctl = (struct xmms2_control *)cl->c_data;
  GSList *list;

  if(ctl->lasterr)
    xmmsc_result_unref(ctl->lasterr);
  if(ctl->con)
    xmmsc_unref(ctl->con);

  ctl->connected = FALSE;
  g_free(ctl->ipcpath);
  g_free(ctl->execpath);
  __free_config(ctl->config);
  list = ctl->file_changed;
  g_slist_foreach(list, __free_elem, NULL);
  g_slist_free(ctl->file_changed);
  list = ctl->started;
  g_slist_foreach(list, __free_elem, NULL);
  g_slist_free(ctl->started);
  list = ctl->stoped;
  g_slist_foreach(list, __free_elem, NULL);
  g_slist_free(ctl->stoped);
  list = ctl->exited;
  g_slist_foreach(list, __free_elem, NULL);
  g_slist_free(ctl->exited);
  g_free(ctl);
  g_free(cl);
}

struct client_plugin_info CLIENT_EXPORT_SYMBOL = {
  .init = client_xmms2_init,
  .exit = client_xmms2_exit,

  .player_name = "XMMS2",
  .author = "X Q",
  .version = "0.1",
  .mail = "crs.chin@gmail.com",
  .description = "plugin for interact with XMMS2 music player server."
};

///////////////////// TESTING CODE ///////////////////////////////
#if defined(TEST_CLIENT_XMMS2)

int main(int argc, char **argv)
{
  struct client_plugin_info *info = &PLUGIN_EXPORT_SYMBOL;
  struct client *client;

  client = (info->init)("test_xmms2_client_client", NULL, argc, argv);
  if(! client)  {
    printf("** error init plugin.\n");
    return -1;
  }

  if(argc < 2)  {
    (info->exit)(client);
    return 0;
  }

  (client->perror)(client->c_data);

  if(! strcmp(argv[1], "start"))
    (client->play)(client->c_data);
  else if(! strcmp(argv[1], "stop"))
    (client->stop)(client->c_data);
  else if(! strcmp(argv[1], "pause"))
    (client->pause)(client->c_data);
  else if(! strcmp(argv[1], "toggle"))
    (client->toggle)(client->c_data);
  else if(! strcmp(argv[1], "quit"))
    (client->quit)(client->c_data);
  else if(! strcmp(argv[1], "next"))
    (client->next)(client->c_data);  
  else if(! strcmp(argv[1], "prev"))
    (client->prev)(client->c_data);
  else if(! strcmp(argv[1], "time"))
    printf("time: %d\n", (client->time)(client->c_data));
  else if(! strcmp(argv[1], "file"))
    (client->play_music_file)(client->c_data, argv[2]);
  else if(! strcmp(argv[1], "volume"))
    printf("volume: %d.\n", (client->volume_get)(client->c_data,VOLUME_CHANNEL_ALL));
  else if(! strcmp(argv[1], "volume_set"))
    (client->volume_set)(client->c_data,VOLUME_CHANNEL_ALL, strtol(argv[2], NULL, 10));
  else if(! strcmp(argv[1], "filename"))
    printf("filename: %s\n", (client->get_music_file)(client->c_data));


  puts("==========================");
  (client->perror)(client->c_data);

  (info->exit)(client);
  return 0;
}

#endif
